// @Author karl
// @Date 2025/9/20 10:49:00
// @Desc 基于Zap和Lumberjack的日志客户端，支持每小时自动切割
package zaplog

import (
	"context"
	"fmt"
	"g_group-buy-market/internal/pkg/config"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"sync"
	"time"
)

// ZapLogClient 日志客户端
type ZapLogClient struct {
	logger         *zap.Logger
	ctx            context.Context
	cfg            *config.ZapLogConfig
	ticker         *time.Ticker
	stopCh         chan struct{}
	wg             sync.WaitGroup
	lumberjackHook *lumberjack.Logger
}

// NewZapLogClient 创建新的Zap日志客户端
func NewZapLogClient(ctx context.Context, cfg *config.ZapLogConfig) (*ZapLogClient, error) {
	// 创建Lumberjack日志写入器
	hook := &lumberjack.Logger{
		Filename:   cfg.LogDir + "/service.log",
		MaxSize:    cfg.MaxSize,
		MaxBackups: cfg.MaxBackup,
		MaxAge:     cfg.MaxAge,
		LocalTime:  true,
		Compress:   cfg.Compress,
	}

	// 创建Zap编码器配置
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	// 创建Zap核心
	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(encoderConfig),
		zapcore.AddSync(hook),
		zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
			return lvl >= cfg.LogLevel
		}),
	)

	// 创建Zap Logger
	logger := zap.New(core, zap.AddCaller())

	client := &ZapLogClient{
		logger:         logger,
		ctx:            ctx,
		cfg:            cfg,
		stopCh:         make(chan struct{}),
		lumberjackHook: hook,
	}

	// 如果需要每小时切割，启动定时器
	if cfg.HourlyRotate {
		client.startHourlyRotation()
	}

	return client, nil
}

// startHourlyRotation 启动每小时切割任务
func (z *ZapLogClient) startHourlyRotation() {
	z.wg.Add(1)
	go func() {
		defer z.wg.Done()

		// 计算到下一个整点的时间
		now := time.Now()
		nextHour := now.Truncate(time.Hour).Add(time.Hour)
		initialDelay := nextHour.Sub(now)

		// 等待第一个整点
		select {
		case <-time.After(initialDelay):
			// 到达第一个整点，执行第一次切割
			z.rotateLog()
		case <-z.stopCh:
			return
		case <-z.ctx.Done():
			return
		}

		// 设置每小时触发一次的定时器
		z.ticker = time.NewTicker(time.Hour)
		defer z.ticker.Stop()

		for {
			select {
			case <-z.ticker.C:
				z.rotateLog()
			case <-z.stopCh:
				return
			case <-z.ctx.Done():
				return
			}
		}
	}()
}

// rotateLog 执行日志切割
func (z *ZapLogClient) rotateLog() {
	if err := z.lumberjackHook.Rotate(); err != nil {
		// 使用标准输出记录错误，因为此时日志系统可能不可用
		fmt.Printf("日志切割失败: %v, 时间: %s\n", err, time.Now().Format(time.DateTime))
	} else {
		z.logger.Info("日志切割完成",
			zap.String("time", time.Now().Format(time.DateTime)),
			zap.String("filename", z.lumberjackHook.Filename))
	}
}

// GetLogger 获取Zap Logger实例
func (z *ZapLogClient) GetLogger() *zap.Logger {
	return z.logger
}

// Sync 刷新缓冲区
func (z *ZapLogClient) Sync() error {
	return z.logger.Sync()
}

// Close 关闭日志客户端
func (z *ZapLogClient) Close() error {
	// 发送停止信号
	close(z.stopCh)

	// 等待所有goroutine结束
	z.wg.Wait()

	// 同步日志缓冲区
	if err := z.logger.Sync(); err != nil {
		return err
	}

	// 关闭Lumberjack
	return z.lumberjackHook.Close()
}

// GetCurrentLogFile 获取当前日志文件信息
func (z *ZapLogClient) GetCurrentLogFile() string {
	return z.lumberjackHook.Filename
}

// ManualRotate 手动触发日志切割
func (z *ZapLogClient) ManualRotate() error {
	return z.lumberjackHook.Rotate()
}

// 辅助函数：创建生产环境使用的Logger
func NewProductionZapLogClient(ctx context.Context, cfg *config.ZapLogConfig) (*ZapLogClient, error) {
	client, err := NewZapLogClient(ctx, cfg)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// 辅助函数：创建开发环境使用的Logger
func NewDevelopmentZapLogClient(ctx context.Context, cfg *config.ZapLogConfig) (*ZapLogClient, error) {
	// 可以在这里调整开发环境的配置
	cfg.LogLevel = zapcore.DebugLevel // 开发环境使用Debug级别

	client, err := NewZapLogClient(ctx, cfg)
	if err != nil {
		return nil, err
	}
	return client, nil
}
